// Package vois impliments vois markup files.
package vois

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
)

// UnMarshal a vois.xml file into a *VOISData
func UnMarshal(vois []byte) (*VOISData, error) {
	var rdr *bytes.Reader
	var dec *xml.Decoder
	var token xml.Token
	var err error
	var vd *VOISData
	var se xml.StartElement
	rdr = bytes.NewReader(vois)
	dec = xml.NewDecoder(rdr)
	vd = nil
	for {
		token, err = dec.Token()
		if err != nil {
			break
		}
		switch token.(type) {
		case xml.ProcInst, xml.CharData:
			// don't process
		case xml.StartElement:
			err = buildVOISError("Missing vois tag at beginning of vois.xml file.")
			se = token.(xml.StartElement)
			vd, err = parseVOIS(dec, se)
			if err == nil || err == io.EOF {
				if len(vd.SiteURL) == 0 {
					err = buildVOISError("missing site url in vois.xml file.")
				} else if len(vd.SiteName) == 0 {
					err = buildVOISError("missing site name in vois.xml file.")
				} else if len(vd.Content) == 0 {
					err = buildVOISError("missing content in vois.xml file.")
				}
			}
			break
		}
	}
	if err == io.EOF {
		err = nil
	}
	return vd, err
}

// Parse <vois>...</vois> tags.
func parseVOIS(dec *xml.Decoder, e xml.StartElement) (*VOISData, error) {
	var token xml.Token
	var se xml.StartElement
	var cd xml.CharData
	var ee xml.EndElement
	var err error
	var data *VOISData
	var sayasat VOISSayasAt
	var sayastext VOISSayasText
	var bfr *bytes.Buffer
	var vc *VOISContent
	var ok bool
	if e.Name.Local != "vois" {
		return nil, buildVOISError("missing vois start tag.")
	}
	data = newVOISData()
	for {
		token, err = dec.Token()
		if err != nil {
			break
		}
		token = xml.CopyToken(token)
		switch token.(type) {
		case xml.CharData:
			// don't process white space chardata between tags
		case xml.ProcInst:
			err = buildVOISError("Processing instruction inside <vois> tags")
			break
		case xml.Comment:
			// don't process comments
		case xml.Directive:
			err = buildVOISError("Directive inside <vois> tags")
			break
		case xml.StartElement:
			se, ok = token.(xml.StartElement)
			if se.Name.Local == "sayas-text" {
				sayastext, err = processSayasText(dec, se)
				if err != nil {
					break
				} else {
					data.SayasText = append(data.SayasText, sayastext)
				}
			} else if se.Name.Local == "sayas-at" {
				sayasat, err = processSayasAt(dec, se)
				if err != nil {
					break
				} else {
					data.SayasAt = append(data.SayasAt, sayasat)
				}
			} else if se.Name.Local == "siteurl" {
				token, err = dec.Token()
				if err != nil {
					break
				}
				token = xml.CopyToken(token)
				switch token.(type) {
				case xml.CharData:
					cd, ok = token.(xml.CharData)
					bfr = bytes.NewBuffer(cd)
					data.SiteURL = bfr.String()
					// get the end tag
					token, err = dec.Token()
					if err != nil {
						break
					}
					token = xml.CopyToken(token)
					ee, ok = token.(xml.EndElement)
					if ee.Name.Local != "siteurl" {
						err = buildVOISError(fmt.Sprintf("unmatched </%s>", ee.Name.Local))
						break
					}
				case xml.StartElement, xml.EndElement, xml.Attr, xml.Comment, xml.ProcInst, xml.Directive:
					err = buildVOISError("<siteurl> is missing the character data.")
					break
				}
			} else if se.Name.Local == "sitename-sayas" {
				token, err = dec.Token()
				if err != nil {
					break
				}
				token = xml.CopyToken(token)
				switch token.(type) {
				case xml.CharData:
					cd, ok = token.(xml.CharData)
					bfr = bytes.NewBuffer(cd)
					data.SiteName = bfr.String()
					// get the end tag
					token, err = dec.Token()
					if err != nil {
						break
					}
					token = xml.CopyToken(token)
					ee, ok = token.(xml.EndElement)
					if ee.Name.Local != "sitename-sayas" {
						err = buildVOISError(fmt.Sprintf("unmatched </%s>", ee.Name.Local))
						break
					}
				case xml.StartElement, xml.EndElement, xml.Attr, xml.Comment, xml.ProcInst, xml.Directive:
					err = buildVOISError("<sitename-sayas> is missing the character data.")
					break
				}
			} else if se.Name.Local == "article" {
				vc, err = processArticle(dec, se)
				if err != nil {
					break
				} else {
					data.Content = append(data.Content, vc)
				}
			} else if se.Name.Local == "navigation" {
				vc, err = processNavigation(dec, se)
				if err != nil {
					break
				} else {
					data.Content = append(data.Content, vc)
				}
			} else if se.Name.Local == "form" {
				vc, err = processForm(dec, se)
				if err != nil {
					return data, err
				} else {
					data.Content = append(data.Content, vc)
				}
			} else if se.Name.Local == "rss-navigation" {
				vc, err = processRSSNavigation(dec, se)
				if err != nil {
					return data, err
				} else {
					data.Content = append(data.Content, vc)
				}
			} else if se.Name.Local == "atom-navigation" {
				vc, err = processAtomNavigation(dec, se)
				if err != nil {
					return data, err
				} else {
					data.Content = append(data.Content, vc)
				}
			}
		case xml.EndElement:
			ee, ok = token.(xml.EndElement)
			if ok == false {
				err = buildVOISError("token to end element error.")
				break
			}
			if ee.Name.Local == "vois" {
				break
			} else {
				err = buildVOISError(fmt.Sprintf("unmatched </%s>", ee.Name.Local))
				break
			}
		}
	}
	return data, err
}
